package com.example.demo.util;

import com.example.demo.config.CertConstants;
import com.example.demo.config.Configuration;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.util.DerValue;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;

import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Enumeration;

/**
 * 从证书文件中获取公私钥单元类
 *
 * @author miwm.sdc
 * @date 2017年12月13日 下午3:21:22
 */
public class CertFileUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(CertFileUtil.class);

    /** 签名证书 */
    private static X509Certificate signCert;
    /** 签名私钥 */
    private static PrivateKey signPrivateKey;

    private static String digestAlg = "SHA256";

    private static String getDigestAlg() {
        return digestAlg;
    }

    public static void setDigestAlg(String digestAlg) {
        CertFileUtil.digestAlg = digestAlg;
    }

    /**
     * 从cer文件中获取公钥
     *
     * @param cerFileName 公钥证书文件
     */
    public static String getPublicKeyFormCerFile(String cerFileName) {
        //            文件路径转换
        cerFileName = ClassLoader.getSystemResource(cerFileName).getPath().substring(1);
        Path path = Paths.get(cerFileName);
        try (InputStream inStream = Files.newInputStream(path)) {
            CertificateFactory instance = CertificateFactory.getInstance(CertConstants.CERT_TYPE);
            Certificate certificate = instance.generateCertificate(inStream);
            PublicKey publicKey = certificate.getPublicKey();
            return Base64.encodeBase64String(publicKey.getEncoded());
        } catch (CertificateException | IOException e){
            LOGGER.error("get public key from cer file error ", e);
        }

        return null;
    }

    /**
     * 从pfx文件中获取私钥
     *
     * @param pfxFileName 证书文件名称
     * @param pfxPassword 证书密钥
     */
    public static String getPrivateKeyFromPfxFile(String pfxFileName, String pfxPassword) {
        try {
            KeyStore keyStore = getKeyStore(pfxFileName, pfxPassword);
            String keyAlias = getKeyAlias(keyStore);
            PrivateKey privateKey = (PrivateKey)keyStore.getKey(keyAlias, pfxPassword.toCharArray());
            return Base64.encodeBase64String(privateKey.getEncoded());
        } catch (Exception e) {
            LOGGER.error("get private key from pfx file error ", e);
        }

        return null;
    }

    /**
     * 获取证书库
     *
     * @param pfxFileName 证书文件名
     * @param pfxPassword 证书密码
     */
    private static KeyStore getKeyStore(String pfxFileName, String pfxPassword) throws Exception {
        //            文件路径转换
        pfxFileName = ClassLoader.getSystemResource(pfxFileName).getPath().substring(1);
        Path path = Paths.get(pfxFileName);
        try (InputStream inStream = Files.newInputStream(path)) {
            KeyStore keyStore = KeyStore.getInstance(CertConstants.KEY_STORE_TYPE);
            keyStore.load(inStream, pfxPassword.toCharArray());
            return keyStore;
        }
    }

    /**
     * 获取证书别名
     *
     * @param keyStore 证书库
     */
    private static String getKeyAlias(KeyStore keyStore) throws KeyStoreException {
        String keyAlias = null;
        Enumeration<String> aliases = keyStore.aliases();
        if (aliases.hasMoreElements()) {
            keyAlias = aliases.nextElement();
        }
        return keyAlias;
    }

    /**
     * 根据公钥字符串生成公钥对象
     *
     * @param publicKey 公钥字符串
     */
    public static PublicKey toPublicKey(String publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(CertConstants.ALGORITHM_RSA);
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        // 生成公钥对象
        return keyFactory.generatePublic(x509KeySpec);
    }

    /**
     * 根据私钥字符串生成私钥对象
     *
     * @param privateKey 私钥字符串
     */
    public static PrivateKey toPrivateKey(String privateKey) throws Exception {
        // 对密钥解密
        byte[] keyBytes = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(CertConstants.ALGORITHM_RSA);
        return keyFactory.generatePrivate(pkcs8KeySpec);
    }

    /**
     * pkcs7签名,包括了证书信息，如序列号、证书颁发者和持有者
     *
     * @param originData 源数据字节数组
     */
    public static String detachSign(byte[] originData) throws Exception {
        Signature signature = Signature.getInstance(getDigestAlg() + "with" + CertConstants.ALGORITHM_RSA);
        signature.initSign(getSignPrivateKey());
        signature.update(originData, 0, originData.length);
        return base64Encode(createPkcs7(null, signature.sign()));
    }

    /**
     * 获取私钥
     */
    private static PrivateKey getSignPrivateKey() {
        if (signPrivateKey == null) {
            initializeCertificate(Configuration.pfxFileName, Configuration.pfxPassword);
        }
        return signPrivateKey;
    }

    /**
     * 获取签名证书
     */
    private static X509Certificate getSignCert() {
        if (signCert == null) {
            initializeCertificate(Configuration.pfxFileName, Configuration.pfxPassword);
        }
        return signCert;
    }

    /**
     * 初始化证书及私钥
     *
     * @param pfxFileName 证书文件名称
     * @param pfxPassword 证书文件密码
     */
    private static void initializeCertificate(String pfxFileName, String pfxPassword) {
        try {
            KeyStore keyStore = getKeyStore(pfxFileName, pfxPassword);
            String keyAlias = getKeyAlias(keyStore);
            signCert = (X509Certificate)keyStore.getCertificate(keyAlias);
            signPrivateKey = (PrivateKey)keyStore.getKey(keyAlias, pfxPassword.toCharArray());
        } catch (Exception e) {
            LOGGER.error("cert initialize error ", e);
        }
    }

    private static String base64Encode(byte[] data) {
        String strRet = Base64.encodeBase64String(data);
        strRet = strRet.replaceAll("\r\n", "");
        strRet = strRet.replaceAll("\n", "");
        strRet = strRet.replaceAll(" ", "");
        strRet = strRet.replaceAll("\t", "");
        return strRet;
    }

    /**
     * 构造PKCS7数据
     *
     * @param originData 原始数据，如果不为null，则签名结果将包含被签名数据，否则不包含
     * @param signedData 签名数据
     */
    private static byte[] createPkcs7(byte[] originData, byte[] signedData)
            throws NoSuchAlgorithmException, IOException {
        AlgorithmId[] digestAlgorithmIds = new AlgorithmId[]{AlgorithmId.get(getDigestAlg())};
        ContentInfo contentInfo;
        if (originData == null) {
            // Detach方式，不包含被签名数据
            contentInfo = new ContentInfo(ContentInfo.DATA_OID, null);
        } else {
            // Attach方式，包含被签名数据
            contentInfo = new ContentInfo(ContentInfo.DATA_OID,
                    new DerValue((byte) CertConstants.DER_VALUE_BYTE, originData));
        }

        X509Certificate[] certificates = new X509Certificate[]{getSignCert()};
        SignerInfo si = new SignerInfo((X500Name) getSignCert().getIssuerDN(), getSignCert().getSerialNumber(),
                AlgorithmId.get(getDigestAlg()), null,
                new AlgorithmId(AlgorithmId.RSAEncryption_oid), signedData, null);
        SignerInfo[] signerInfos = new SignerInfo[]{si};
        PKCS7 p7 = new PKCS7(digestAlgorithmIds, contentInfo, certificates, signerInfos);
        try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) {
            p7.encodeSignedData(bout);
            return bout.toByteArray();
        }
    }

    /**
     * 验签 无须使用验签公钥字符串，直接根据签名原文和签名结果进行验签
     *
     * @param originData 签名原文
     * @param signedData 签名结果
     * @return 验签是否通过
     */
    public static boolean detachVerify(byte[] originData, String signedData)
            throws NoSuchAlgorithmException, SignatureException, IOException{
        if (signedData == null) {
            throw new IllegalArgumentException("signedData can't be null");
        } else {
            PKCS7 exp = new PKCS7(Base64.decodeBase64(signedData));
            SignerInfo[] signers;
            if (originData == null) {
                signers = exp.verify();
            } else {
                signers = exp.verify(originData);
            }

            if (signers != null) {
                X509Certificate[]certs = exp.getCertificates();
                return certs[0].getSerialNumber().equals(signers[0].getCertificateSerialNumber());
            } else {
                return false;
            }
        }
    }

    public static void main(String[] args) throws IOException {
//        System.out.println("公钥1：\n"+getPublicKeyFormCerFile("E:\\WorkSpace\\富周息产品\\富周息证书文件\\Fumin_Public_CFCA_sandbox.cer"));
//        System.out.println("公钥2：\n"+getPublicKeyFormCerFile("E:\\WorkSpace\\富周息产品\\富周息证书文件\\public_sign_key_sandbox.cer"));
        String path = ClassLoader.getSystemResource("static/CgIqKl7HMk-AaXE9AAALotBSoH0417.pfx").getPath().substring(1);
        System.out.println("私钥：\n"+getPrivateKeyFromPfxFile(path,"11111111"));
    }

}
